还没有笔记
选中页面文字后点击「高亮」按钮添加
COMS W3157
Dr. Borowski
在C程序中,main函数中的return
_exit(int)和_Exit(int)是系统调用。
exit (int)是一个C标准库函数。
```
#include "apue.h"
static void my_exit1(void);
static void my_exit2(void);
int
main(void)
{
if (atexit(my_exit2) != 0)
err_sys("can't register my_exit2");
if (atexit(my_exit1) != 0)
err_sys("can't register my_exit1");
if (atexit(my_exit1) != 0)
err_sys("can't register my_exit1");
printf("main is done\n");
return(0);
}
static void
my_exit1(void)
{
printf("first exit handler\n");
}
static void
my_exit2(void)
{
printf("second exit handler\n");
```
```
}
```

图 7.3 退出处理程序示例

图 7.2 C程序如何启动和终止
$ ./a.out arg1 arg2 34
int main(int argc, char argv[], char envp[])
envp
argv

envp提供了程序的环境。
它与env的值相同。
尝试在shell中export。
每个进程都有一个唯一的进程ID (PID)。
PID由内核分配。
已终止进程的ID可以重用。
可以使用ps命令列出当前运行的进程。
```
$ ps
21271 pts/0 00:00:00 bash
21308 pts/0 00:00:00 ps
```
/sbin/init协调其余的启动进程并为用户配置环境。当init命令启动时,它成为系统上所有自动启动进程的父进程或祖父进程。
| 用户 | PID | %CPU | %MEM | VSZ | RSS TTY | | STAT | START | TIME COMMAND | |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| root | 1 | 0.0 | 0.0 | 176988 | 3264 | ? | Ss | Feb26 | 0:03 | /sbin/init |
| root | 2 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 0:00 [kthreadd] |
| root | 3 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 0:09 [ksoftirqd/0] |
| root | 5 | 0.0 | 0.0 | 0 | 0 | ? | S< | Feb26 | | 0:00 [kworker/0:0H] |
| root | 7 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 1:13 [rcu_sched] |
| root | 8 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 0:00 [rcu_bh] |
| root | 9 | 0.0 | 0.0 | 0 | 0 | ? | S | Feb26 | | 0:00 [migration/0] |
| user | 14965 | 0.0 | 0.1 | 1039740 | 15852 | ? | Sl | Feb29 | | 0:05 texstudio --start-always |
| user | 15626 | 0.0 | 0.0 | 128484 | 5144 | ? | S | Feb29 | | 0:00 orage |
| root | 17886 | 0.0 | 0.0 | 86644 | 3728 | ? | Ss | 07:35 | | 0:00 /usr/sbin/cupsd -f |
| user | 18016 | 4.1 | 7.8 | 1769348 | 639880 | ? | Sl | 13:19 | | 22:35 /usr/bin/iceweasel |
| user | 18246 | 0.0 | 0.0 | 5964 | 396 | ? | S | 13:23 | 0:00 cat | |
a = 显示所有用户的进程
u = 显示进程的用户/所有者
x = 也显示未连接到终端的进程
pid_t getpid(void);
返回调用(当前)进程的进程ID。
pid_t getppid(void);
返回调用进程的父进程的进程ID。
pid_t fork();
父进程中所有打开的文件描述符都在子进程中复制。

图 8.2 fork后父进程和子进程之间共享打开文件
```
#include
#include
#include
int main() {
pid_t pid;
if ((pid = fork()) < 0) {
perror("fork");
return -1;
} else if (pid == 0) {
/ child /
printf("I am the child\n");
return 0;
}
/ parent /
printf("I am the parent of %d\n", pid);
return 0;
}
```

I am the child
I am the parent of 1200
```
Use of fork()
```
如果注释掉这个return语句会发生什么?
```
#include
#include
#include
int main() {
pid_t pid;
if ((pid = fork()) < 0) {
perror("fork");
return -1;
} else if (pid == 0) {
/ child /
printf("I am the child\n");
}
/ parent /
printf("I am the parent of %d\n", pid);
return 0;
}
```

I am the child
I am the parent of 0
I am the parent of 1200
```
#include
#include
#include
int main() {
pid_t pid;
int w = 100;
if ((pid = fork()) < 0) {
perror("fork");
return -1;
} else if (pid == 0) {
/ child /
w = 200;
} else {
/ parent /
w = -100;
}
printf("I am %d, child of %d, w = %d\n",
getpid(), getppid(), w);
return 0;
}
```
```
#include #include #include I am 22982, child of 13673, w = -100 int main() { pid_t pid; int w = 100; if ((pid = fork()) < 0) { perror("fork"); return -1; } else if (pid == 0) { / child / w = 200; } else { / parent / w = -100; } printf("I am %d, child of %d, w = %d\n", getpid(), getppid(), w); return 0; } ```
```
#include "apue.h"
int globvar = 6; / external variable in initialized data /
char buf[] = "a write to stdout\n";
int
main(void)
{
int var; / automatic variable on the stack /
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf)-l) != sizeof(buf)-l)
err_sys("write error");
printf("before fork\n"); / we don't flush stdout /
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { / child /
globvar++; / modify variables /
var++;
} else {
sleep(2); / parent /
}
printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,
var);
exit(0);
}
```
图 8.1 fork函数示例
```
$ ./a.out
a write to stdout
before fork
pid = 430, glob = 7, var = 89 child's variables were changed
pid = 429, glob = 6, var = 88 parent's copy was not changed
$ ./a.out > temp.out
$ cat temp.out
a write to stdout
before fork
pid = 432, glob = 7, var = 89
before fork
pid = 431, glob = 6, var = 88
```
在第二种情况下,fork之前的printf被调用一次,但当fork被调用时,该行仍保留在缓冲区中。当父进程的数据空间复制到子进程时,此缓冲区也会复制到子进程。现在,父进程和子进程都有一个包含此行的标准I/O缓冲区。
pid_t wait(int *wstatus);
| 宏 | 描述 |
| :--- | :--- |
| WIFEXITED (status) | 如果status是为正常终止的子进程返回的,则为True。在这种情况下,我们可以执行
WEXITSTATUS (status)
来获取子进程传递给exit、_exit或_Exit的参数的低8位。 |
| WIFSIGNALED (status) | 如果status是为异常终止的子进程返回的,由它未捕获到的信号引起。在这种情况下,我们可以执行
WTERMSIG(status)
来获取导致终止的信号编号。
此外,一些实现(但不是Single UNIX Specification)定义了宏
WCOREDUMP (status)
如果终止进程生成了核心文件,则返回true。 |
| WIFSTOPPED (status) | 如果status是为当前已停止的子进程返回的,则为True。在这种情况下,我们可以执行
WSTOPSIG(status)
来获取导致子进程停止的信号编号。 |
| WIFCONTINUED (status) | 如果status是为作业控制停止后已继续的子进程返回的(XSI选项;仅限waitpid)。 |
图 8.4 检查wait和waitpid返回的终止状态的宏
pid_t waitpid(pid_t pid, int *status, int options);
如果pid:
wait(&status)等价于waitpid(-1, &status, 0)
waitpid只等待已终止的子进程,除非options是:
WNOHANG 如果没有子进程退出,则立即返回。
WUNTRACED 如果子进程已停止,也返回。
WCONTINUED 如果已停止的子进程已通过发送SIGCONT恢复,也返回。
exec()函数族用新的进程映像替换当前进程映像。当进程调用exec()函数时,该进程保留其进程ID及其与父进程的关系。然而,其内存地址空间被新可执行文件的内存地址空间取代,新程序开始执行其main()函数。
1 - 接收参数列表
```
execl(), execlp(), execle()
```
v - 接收参数向量
```
execv(), execvp(), execvpe()
```
e - 通过envp参数接收环境
```
execle(), execvpe()
```
p - 如果文件名不包含/,则在PATH环境变量中搜索可执行文件
```
execlp(), execvp(), execvpe()
```
```
#include
int execl(const char pathname, const char arg0, ... /*
(char )0 / );
int execv(const char pathname, char const argv []);
int execle(const char pathname, const char arg0, ... /*
(char )0, char const envp[] */ );
int execve(const char pathname, char const argv[], char
*const envp []);
int execlp(const char filename, const char arg0, ... /*
(char )0 / );
int execvp(const char filename, char const argv []);
int fexecve(int fd, char const argv[], char const
envp[]);
```

图 8.15 七个exec函数的关系
```
void function1() {
pid_t pid;
if ((pid = fork()) == 0) {
sleep(1);
printf("C1 ");
fflush(stdout);
}
if ((pid = fork()) == 0) {
sleep(2);
printf("C2 ");
fflush(stdout);
}
if ((pid = fork()) == 0) {
sleep(3);
printf("C3 ");
fflush(stdout);
}
// Small hack to wait for all
// descendent processes
while (wait(NULL) != -1) { }
}
```
输出是什么?
它具有确定性吗?
此函数执行需要多长时间?
提示:画一个图!
```
void function1() {
pid_t pid;
if ((pid = fork()) == 0) {
sleep(1);
printf("C1 ");
fflush(stdout);
}
if ((pid = fork()) == 0) {
sleep(2);
printf("C2 ");
fflush(stdout);
}
if ((pid = fork()) == 0) {
sleep(3);
printf("C3 ");
fflush(stdout);
}
// Small hack to wait for all
// descendent processes
while (wait(NULL) != -1) { }
}
```

输出:C1 C2 C3 C2 C3 C3 C3
```
void function1() {
pid_t pid;
if ((pid = fork()) == 0) {
sleep(1);
printf("C1 ");
fflush(stdout);
}
if ((pid = fork()) == 0) {
sleep(2);
printf("C2 ");
fflush(stdout);
}
if ((pid = fork()) == 0) {
sleep(3);
printf("C3 ");
fflush(stdout);
}
// Small hack to wait for all
// descendent processes
while (wait(NULL) != -1) { }
}
```

多条路径加起来需要 3 秒,因此这是非确定性的。
另一个输出是:C1 C2 C2 C3 C3 C3 C3
```
void function1() {
pid_t pid;
if ((pid = fork()) == 0) {
sleep(1);
printf("C1 ");
fflush(stdout);
}
if ((pid = fork()) == 0) {
sleep(2);
printf("C2 ");
fflush(stdout);
}
if ((pid = fork()) == 0) {
sleep(3);
printf("C3 ");
fflush(stdout);
}
// Small hack to wait for all
// descendent processes
while (wait(NULL) != -1) { }
}
```

总执行时间:最长路径 6 秒
```
void function2() {
if (fork() || fork()) { What is the output?
fork();
} Is it deterministic?
printf("1 ");
fflush(stdout); How long does this function take to
while (wait(NULL) != -1) { } execute?
}
```
```
void function2() {
if (fork() || fork()) {
fork();
}
printf("1 ");
fflush(stdout);
while (wait(NULL) != -1) { }
}
```

输出是什么? $\mathbf{11111}$
它具有确定性吗? 是的,因为只打印 1。
此函数执行需要多长时间? 几乎是瞬间的。
```
void function3() {
if (fork() && fork()) { What is the output?
fork();
} Is it deterministic?
printf("2 ");
fflush(stdout); How long does this function take to
while (wait(NULL) != -1) { } execute?
}
```
```
void function3() {
if (fork() && fork()) {
fork();
}
printf("2 ");
fflush(stdout);
while (wait(NULL) != -1) { }
}
```

输出是什么? 2222
它具有确定性吗? 是的,因为只打印 $\mathbf{2s}$。
此函数执行需要多长时间? 几乎是瞬间的。
```
void function4() {
pid_t pid[2] = {fork(), fork()};
for (int i = 0 ; i < 2; i++) {
printf("%d", pid[i] == 0); Is it deterministic?
fflush(stdout);
}
while (wait(NULL) != -1) {};
}
What is the output?
How long does this function take to execute?
```
```
void function4() {
pid_t pid[2] = {fork(), fork()};
for (int i = 0 ; i < 2; i++) {
printf("%d", pid[i] == 0);
fflush(stdout);
}
while (wait(NULL) != -1) {};
}
```

输出是什么? $\mathbf{8}$位数字 - $\mathbf{4}$个零,$\mathbf{4}$个一,来自上面显示的成对。数字可以是任意顺序,甚至可以是交错的对。
它具有确定性吗? 否。
此函数执行需要多长时间? 几乎是瞬间的。